//          《 面向过程进程池 》



//		《 Task.hpp 》


#pragma once

#include <iostream>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>


#define TaskNum 3

typedef void (*task_t) ();  //task_t 函数指针类型

void Print()    //打印
{
    std::cout << "I am print task" << std::endl;
}
void DownLoad() //下载
{
    std::cout << "I am a download task" << std::endl;
}
void Flush()    //刷新
{
    std::cout << "I am a flush task" << std::endl;
}


task_t tasks[TaskNum];

//加载任务
void LoadTask()
{
    //产生随机数
    srand(time(nullptr) ^ getpid() ^ 17777);    // ^ 增加随机性
    tasks[0] = Print;
    tasks[1] = DownLoad;
    tasks[2] = Flush;
}

//执行任务
void ExcuteTask(int number)
{
    if(number < 0 || number > TaskNum - 1) return;
    tasks[number] ();
}

//随机选择任务
int SelectTask()
{
    return rand() % TaskNum;
}

//将管道的读端，重定向到标准输入后，子进程就没有管道的概念了。不需要从 rfd 里去读了
void work()
{   //让子进程做的事
    while (true) 
    {
        int command = 0;
        int n = read(0, &command, sizeof(command));     //读文件都从标准输入去读
        if(n == sizeof(int))
        {
            std::cout << "pid is : " << getpid() << " handler task" << std::endl;
            ExcuteTask(command);
        }
        else if(n == 0)      //如果读到0，表示读到末尾结束。
        {
            std::cout << "sub process : " << getpid() << " quit" << std::endl;
            break;
        }
    }
}


//——————————————————————————————————————————————


//——————————————————————————————————————————————



//		《 ProcessPool.cc 》



#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

// void work(int rfd)       //写的 work 的方法本身也是一种任务，转到 Task.hpp  
// {   //让子进程做的事
//     while (true) 
//     {
//         int command = 0;
//         int n = read(rfd, &command, sizeof(command));
//         if(n == sizeof(int))
//         {
//             std::cout << "pid is : " << getpid() << " handler task" << std::endl;
//             ExcuteTask(command);
//         }
//         else if(n == 0)     //如果读到0，表示读到末尾结束。
//         {
//             std::cout << "sub process : " << getpid() << " quit" << std::endl;
//             break;
//         }
//     }
// }


//master 
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string& name)
        :_wfd(wfd), _subprocessid(id), _name(name)
    { }
    int GetWfd() { return _wfd; }                   //获得写入文件描述符
    pid_t GetProcessId() { return _subprocessid; }  //子进程pid
    std::string GetName() { return _name; }           //名字
    
    void CloseChannel()
    {
        close(_wfd);    //关闭写端 文件描述符
    }
    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0); //(等谁， 退出码， 当代方式)
        if(rid > 0)
        {
            std::cout << "wait " << rid << " success" << std::endl;
        }
    }
    ~Channel()
    {

    }

private:
    int _wfd;               //写文件描述符
    pid_t _subprocessid;    //子进程ID 
    std::string _name;      //给信道起名字
};

// 形参类型和命名规范
// const &  输入型参数
// &        输入输出型参数
// *        输出型参数

// //创建信道和子进程
// void CreateChannelAndSub(int num, std::vector<Channel> *channels)
// {
//     //BUG?
//    //创建信道和子进程
//     for (int i = 0; i < num; i++)
//     {
//         //1.创建管道
//         int pipefd[2] = { 0 };
//         int n = pipe(pipefd);
//         if (n < 0) exit(1);          //管道创建失败，直接终止。

//         //2.创建子进程   
//         pid_t id = fork();
//         // if (id == 0)
//         // {
//         //     close(pipefd[1]);       //子进程关闭 写
//         //     work(pipefd[0]);
//         //     close(pipefd[0]);
//         //     exit(0);
//         // }
//         if (id == 0)
//         {
//             close(pipefd[1]);       //子进程关闭 写
//             dup2(pipefd[0], 0);     //将管道的读端，重定向到标准输入 
//             work();
//             close(pipefd[0]);
//             exit(0);
//         }

//         //3.构建Channel名字
//         std::string channel_name = "Channel_" + std::to_string(i);

//         //父进程 关闭读，(让父进程写，子进程读)
//         close(pipefd[0]);
//         //a.子进程的pid,    b.父进程关系的管道的w端
//         channels->push_back(Channel(pipefd[1], id, channel_name));
//     }
// }

//task_t task :回调函数
//创建信道和子进程
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{
    //BUG?
   //创建信道和子进程
    for (int i = 0; i < num; i++)
    {
        //1.创建管道
        int pipefd[2] = { 0 };
        int n = pipe(pipefd);
        if (n < 0) exit(1);          //管道创建失败，直接终止。

        //------------------------------------------------------------------------------------------------------------
        // //2.创建子进程   
        // pid_t id = fork();
        // if (id == 0)
        // {
        //     close(pipefd[1]);       //子进程关闭 写
        //     dup2(pipefd[0], 0);     //将管道的读端，重定向到标准输入 
        //     task();                 //所有的子进程全部去执行 task()
        //     close(pipefd[0]);
        //     exit(0);
        // }
        
        //BUF 原因，创建子进程时，子进程继承父进程的文件描述符表，每次创建，文件描述符表中 信号量+1，
        //由于多次继承，信号量累加多次，当释放一次时，信号量-1，信号量 ！= 0，于是进入阻塞等待。
        //2.创建子进程  
        pid_t id = fork();
        if (id == 0)
        {
            if(!channels->empty())          //第一次创建的管道是空，
            {
                //如果不是空，证明是第二次后面创建的管道
                for(auto &channel : *channels)
                {
                    channel.ChoseChannel(); //如果当前子进程不是第一个创建的，就吧继承过来的写端全部关闭掉。（避免影响自己）
                }
            } 
            close(pipefd[1]);       //子进程关闭 写
            dup2(pipefd[0], 0);     //将管道的读端，重定向到标准输入 
            task();                 //所有的子进程全部去执行 task()
            close(pipefd[0]);
            exit(0);
        }
        //------------------------------------------------------------------------------------------------------------

        //3.构建Channel名字
        std::string channel_name = "Channel_" + std::to_string(i);

        //父进程 关闭读，(让父进程写，子进程读)
        close(pipefd[0]);
        //a.子进程的pid,    b.父进程关系的管道的w端
        channels->push_back(Channel(pipefd[1], id, channel_name));
    }
}

int NextChannel(int channelnum)
{
    static int next = 0;
    int channel = next;
    next++;
    next %= channelnum;
    return channel;
}

void SendTaskCommand(Channel&channel, int taskcommand)
{
    write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}



//2.通过Channel控制子进程
void ctrlProcessOnce(std::vector<Channel> &channels)
{
    sleep(1);
    //a.(随机)选择一个任务
    int taskcommand = SelectTask();
    //b.选择一个信道和进程
    int channel_index = NextChannel(channels.size());
    //c.发送任务
    SendTaskCommand(channels[channel_index], taskcommand);   //向指定信道 发送指定任务码。
    
    std::cout << "----------------------------------" << std::endl; 
    std::cout << "taskcommand: " << taskcommand << " channel: " \
    << channels[channel_index].GetName() << " sub process: " \
    << channels[channel_index].GetProcessId() << std::endl; 
}

void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if(times > 0)
    {    
        while(times--)      //如果 times > 0 按照我们的预期控制次数。
        {
            ctrlProcessOnce(channels);
        }
    }
    else
    {
        while(true)         //一直控制
        {
           ctrlProcessOnce(channels);
        }
    }
}

//------------------------------------------------------------------------------------------------------------
// //3.回收管道和子进程，a.关闭所有的写端， b.回收子进程
// void ClearUpChannel(std::vector<Channel>  &channels)
// {
//     for(auto &channel : channels)
//     {
//         channel.CloseChannel(); //关闭所以的写端，子进程全部退进入等待状态。
        
//         //channel.Wait();       //err 这样关闭等待的子进程有个大坑，
//         //由于后续创建的子进程会继承父进程我文件描述符表，越靠前，文件描述符表中的读写端越多，
//         //关闭一次，无法关闭全部读写端。所以会阻塞等待。
//     }
//     //注意：
//     for(auto &channel : channels)
//     {
//         channel.Wait();         //关闭所有等待的子进程。
//     }
// }

//如果就想要这样写(关闭写端就直接退出子进程) 转到 ↑    //创建信道和子进程
void ClearUpChannel(std::vector<Channel>  &channels)
{
    for(auto &channel : channels)   
    {
        channel.CloseChannel(); //关闭所以的写端，子进程全部退进入等待状态。
        channel.Wait();         //为了防止 直接退出时进入 阻塞等待
    }
}

////由于上述 channel.Wait(); 问题，我们可以 倒着 关闭文件描述符表中的读写端文件。
// void ClearUpChannel(std::vector<Channel>  &channels)
// {
//     int num = channels.size() - 1;
//     while(num >= 0)
//     {
//         channels[num].CloseChannel();
//         channels[num--].Wait();
//     }
// }
//------------------------------------------------------------------------------------------------------------

// ./processpool 5  例如想要创建5个进程
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "processnum" << std::endl;
        return 1;
    }

    int num = std::stoi(argv[1]);   //字符串转整数：stoi 
    LoadTask();                     //加载任务

    std::vector<Channel> channels;
    //1.创建信道和子进程
    //CreateChannelAndSub(num, &channels);
    CreateChannelAndSub(num, &channels, work);

    //2.通过Channel控制子进程
    ctrlProcess(channels, 5);

    //3.回收管道和子进程，a.关闭所有的写端， b.回收子进程
    ClearUpChannel(channels);

    //sleep(100);
    return 0;
}

//——————————————————————————————————————————————

// 运行结果：
// [a@192 ProcessPool]$ ./processpool 10
// ----------------------------------
// taskcommand: 2 channel: Channel_9 sub process: 3250
// pid is : 3250 handler task
// I am a flush task
// sub process : 3250 quit
//  ... 一共10个 ...
// sub process : 3241 quit      //sub 子进程全部退出
// wait 3241 success            //等待子进程也全部成功
//  ... 一共10个 ...
// wait 3250 success

// [a@192 ~]$ while :; do ps axj | head -1 && ps axj | grep -i ProcessPool | grep -v grep; echo"--------------------"； sleep 1; done
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
//   2516   3240   3240   2516 pts/0      3240 S+    1000   0:00 ./processpool 10
//   3240   3241   3240   2516 pts/0      3240 S+    1000   0:00 ./processpool 10
//   3240   ... 一共10个 ...
//   3240   3250   3240   2516 pts/0      3240 S+    1000   0:00 ./processpool 10

//——————————————————————————————————————————————

//   注意处：
//   关闭写端后，子进程进入等待，这里不等子进程，直接退了。子进程变成了僵尸进程。  
//   bash: echo--------------------；: command not found...
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
//   2516   5725   5725   2516 pts/0      5725 S+    1000   0:00 ./processpool 10
//   5725   5726   5725   2516 pts/0      5725 Z+    1000   0:00 [processpool] <defunct>   
//   5725   ... 一共10个 ...
//   5725   5735   5725   2516 pts/0      5725 Z+    1000   0:00 [processpool] <defunct>


//——————————————————————————————————————————————

 
